﻿/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using OptixDotNet.Math;

namespace OptixDotNetUtils
{
    public class Camera : ICamera
    {
        protected Vector3 mRight;
        protected Vector3 mUp;
        protected Vector3 mLook;

        protected float mUlength = 0.0f;
        protected float mVlength = 0.0f;

        public Camera()
        {
            TranslationVel = 1.0f;
            RotationVel = 1.0f;

            Aspect = 1.0f;
            Fov = 60.0f;

            Position = Vector3.Zero;
            mRight = Vector3.UnitX;
            mUp = Vector3.UnitY;
            mLook = Vector3.UnitZ;
        }

        public void Yaw( float angle )
        {
            Matrix4x4 rot = Matrix4x4.CreateRotationY( angle );

            mRight = Vector3.TransformNormal( ref mRight, ref rot );
            mLook = Vector3.TransformNormal( ref mLook, ref rot );
        }

        public void Pitch( float angle )
        {
            Matrix4x4 rot = Matrix4x4.CreateRotationAxis( mRight, angle );

            mUp = Vector3.TransformNormal( ref mUp, ref rot );
            mLook = Vector3.TransformNormal( ref mLook, ref rot );
        }

        #region ICamera Members

        public Vector3 Position { get; set; }
        public Vector3 Target { get; set; }
        public Vector3 Right { get { return mRight * mUlength; } set { mRight = value; } }
        public Vector3 Up { get { return mUp * mVlength; } set { mUp = value; } }
        public Vector3 Look { get { return mLook; } set { mLook = value; } }

        public float Aspect { get; set; }
        public float Fov { get; set; }

        public float TranslationVel { get; set; }
        public float RotationVel { get; set; }

        public virtual void Dolly( float dt )
        {
            Position += mLook * ( TranslationVel * dt );
        }

        public virtual void Pan( float dx, float dy )
        {
            dx *= TranslationVel;
            dy *= TranslationVel;

            Position += new Vector3( mRight.X * dx, 0.0f, mRight.Z * dx );
            Position += mUp * dy;
        }

        public virtual void Rotate( float dx, float dy )
        {
            Yaw( dx * RotationVel );
            Pitch( dy * RotationVel );
            BuildView();
        }

        public virtual void LookAt( Vector3 pos, Vector3 target, Vector3 up )
        {
            up.Normalize();

            Vector3 L = target - pos;
            L.Normalize();

            Vector3 R = Vector3.Cross( ref L, ref up );
            Vector3 U = Vector3.Cross( ref R, ref L );

            mUp = U;
            mRight = R;
            mLook = L;
            Position = pos;

            BuildView();
        }

        public virtual void BuildView()
        {
            float wlen = mLook.Length();
            mUlength = wlen * (float)Math.Tan( Fov / 2.0f * Math.PI / 180.0f );
            mVlength = mUlength / Aspect;

            mLook.Normalize();

            mUp = Vector3.Cross( ref mRight, ref mLook );
            mUp.Normalize();

            mRight = Vector3.Cross( ref mLook, ref mUp );
            mRight.Normalize();
        }

        public virtual void CenterOnBoundingBox( BoundingBox box )
        {
            Vector3 eye = box.Center;
            Vector3 target = box.Center;
            Vector3 up = new Vector3( 0.0f, 1.0f, 0.0f );

            eye.Z += 2.0f * ( box.Max - box.Min ).Length();
            LookAt( eye, target, up );
        }

        public virtual void CenterOnBoundingBox( BoundingBox box, float scale )
        {
            Vector3 eye = box.Center;
            Vector3 target = box.Center;
            Vector3 up = new Vector3( 0.0f, 1.0f, 0.0f );

            eye.Z += 2.0f * ( box.Max - box.Min ).Length() * scale;
            LookAt( eye, target, up );
        }

        public virtual void CenterOnBoundingBox( BoundingBox box, Vector3 up, int axis, float scale )
        {
            Vector3 eye = box.Center;
            Vector3 target = box.Center;

            eye[ axis ] += 2.0f * ( box.Max - box.Min ).Length() * scale;
            LookAt( eye, target, up );
        }

        #endregion
    }
}
